/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
/**
 * Software License, Version 1.0
 *
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 *
 *
 *Redistribution and use in source and binary forms, with or without
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For
 * further information contact the Community Grids Lab at
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and
 * wherever such third-party acknowledgments normally appear.
 *
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering,
 * shall not be used to endorse or promote products derived from this software
 * without prior written permission from Indiana University.  For written
 * permission, please contact the Advanced Research and Technology Institute
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering,
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 *
 *
 * Indiana University provides no reassurances that the source code provided
 * does not infringe the patent or any other intellectual property rights of
 * any other entity.  Indiana University disclaims any liability to any
 * recipient for claims brought by any other entity based on infringement of
 * intellectual property rights or otherwise.
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.service.buffering;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import cgl.narada.service.ServiceException;
import cgl.narada.service.ServiceProperties;
import cgl.narada.service.qos.QosEvent;
import cgl.narada.service.qos.impl.QosServiceImpl;

/**
 * This is the implementation of the BufferingService interface.
 * @author Hasan Bulut  - hbulut@indiana.edu
 */
public class BufferingServiceImpl implements BufferingService {
  //~ Instance fields ----------------------------------------------------------

  private BufferHandler     bufferHandler;
  private DefaultParams     defaults          = new DefaultParams();
  private HashMap           synopsisTimeTable = new HashMap();
  private List              buffer            = Collections.synchronizedList(new LinkedList());
  private NBEventComparator eventComparator   = new NBEventComparator();
  private ServiceProperties serviceProperties;
  private QosServiceImpl    qosServiceImpl;

  //  private String            propertiesFile     = "bufferingservice.properties";
  private String sortingAlgo        = "MergeSort";
  private double releaseFactor      = 0.8;
  private int    maxBufferSize      = 2500000; // bytes
  private int    maxNumberOfEntries = 100;
  private int    numberOfEntries    = 0;
  private int    totalBufferSize    = 0;
  private long   maxDuration        = 5000; // milliseconds

  //~ Constructors -------------------------------------------------------------

  /**
   * Generates a new BufferingServiceImpl object.
   *
   * @param qosServiceImpl QosServiceImpl
   */
  public BufferingServiceImpl(QosServiceImpl qosServiceImpl) {
    this.qosServiceImpl = qosServiceImpl;
    serviceProperties   = qosServiceImpl.getServiceProperties();
    loadProperties();
    bufferHandler = new BufferHandler(this);
  }

  //~ Methods ------------------------------------------------------------------

  /**
   * Sets the maximum duration after which the oldest element in the buffer
   * is released
   *
   * @param entryAge long - maximum buffer entry duration
   */
  public void setBufferEntryMaximumDuration(long entryAge) {
    this.maxDuration = entryAge;
  }

  /** Retrieves the maximum duration after which the oldest element in the
   * buffer is released
   */
  public long getBufferEntryMaximumDuration() {
    return maxDuration;
  }

  /**
   * Ensures that events are ordered while enforcing timing consistencies. The
   * consistencies are enforced based on the timestamp of the last event that
   * was released. This has to be EXPLICTLY set.
   *
   * @param contentSynopsis Object - content synopsis
   * @param enforce boolean - strict consistency enforcement
   */
  public void setEnforceTimeOrderingConsistency(Object contentSynopsis,
                                                boolean enforce) {
    if (synopsisTimeTable.containsKey(contentSynopsis)) {
      ((SynopsisTime) synopsisTimeTable.get(synopsisTimeTable)).setEnforce(enforce);
    } else {
      SynopsisTime synopsisTime = new SynopsisTime(enforce, 0);
      synopsisTimeTable.put(synopsisTimeTable, synopsisTime);
    }
  }

  /**
   * Indicates if the buffering service enforces time ordering conistency
   *
   * @param contentSynopsis Object - content synopsis
   *
   * @return boolean - enforcement value for the content synopsis
   */
  public boolean getEnforceTimeOrderingConsistency(Object contentSynopsis) {
    if (synopsisTimeTable.containsKey(contentSynopsis)) {
      return ((SynopsisTime) synopsisTimeTable.get(contentSynopsis)).getEnforce();
    }
    return false;
  }

  /**
   * Sets the maximum buffer size. This corresponds to the total size of all
   * the payloads associated with events in the buffer. This method throws an
   * exception if the specified value is an invalid one
   *
   * @param maxBufferSize int - maximum buffer size in bytes
   *
   * @throws ServiceException
   */
  public void setMaximumBufferSize(int maxBufferSize) throws ServiceException {
    this.maxBufferSize = maxBufferSize;
  }

  /**
   * Returns maximum buffer size.This corresponds to the total size of all the
   * payloads associated with events in the buffer.
   *
   * @return int - maximum buffer size
   */
  public int getMaximumBufferSize() {
    return this.maxBufferSize;
  }

  /**
   * Sets the maximum number of entries in the buffering service. This method
   * throws an exception if the specified value is an invalid one
   * (0 or negative).
   *
   * @param maxEntries int - maximum number of entries
   *
   * @throws ServiceException
   */
  public void setMaximumNumberOfEntries(int maxEntries)
                                 throws ServiceException {
    this.maxNumberOfEntries = maxEntries;
  }

  /**
   * Returns the maximum number of entries
   *
   * @return int - maximum number of entries
   */
  public int getMaximumNumberOfEntries() {
    return maxNumberOfEntries;
  }

  /**
   * Release facotr is a double number between 0 - 1. It should not be less
   * than 0.5. the default value is 1.0. Setting this factor to another value
   * causes BufferHandler to release (factor * number of buffer entries)
   *
   * @param factor double - release factor
   */
  public void setReleaseFactor(double factor) {
    this.releaseFactor = factor;
  }

  /**
   * Returns the release factor of the buffer
   * @return double - release factor
   */
  public double getReleaseFactor() {
    return this.releaseFactor;
  }

  /**
   * Sets the scheme for sorting events in the buffers
   *
   * @param sortAlgo String - String representation of the sorting algorithm.
   * MergeSort, QuickSort, etc.
   *
   * @throws ServiceException
   */
  public void setSortingScheme(String sortAlgo) throws ServiceException {
    this.sortingAlgo = sortAlgo;
  }

  /**
   * Returns the current sorting scheme
   *
   * @return String - sorting algorithm
   */
  public String getSortingScheme() {
    return sortingAlgo;
  }

  /**
   * Returns the total size of the buffer. This corresponds to the total size
   * of the content payloads of all the events within the buffer.
   *
   * @return int - total buffer size
   */
  public int getTotalBufferSize() {
    return totalBufferSize;
  }

  /**
   * Returns the total number of entries currently present within the buffer.
   *
   * @return int - total number of entries in the buffer
   */
  public int getTotalNumberOfEntries() {
    return buffer.size();
  }

  /**
   * Reads default values from DefaultParams class.
   */
  public void loadDefaults() {
    DefaultParams defaultValues = new DefaultParams();
    releaseFactor      = defaultValues.getReleaseFactor();
    maxBufferSize      = defaultValues.getMaxBufferSize();
    maxNumberOfEntries = defaultValues.getMaxNumberOfEntries();
    maxDuration        = defaultValues.getEntryDuration();
    sortingAlgo        = defaultValues.getSortingAlgo();
  }

  /**
   * Reads required params from a properties class.
   */
  public void loadProperties() {
    DefaultParams defaultValues = new DefaultParams();
    releaseFactor = Double.parseDouble(serviceProperties.getProperty("TOB_BufferReleaseFactor")
                                                        .trim());
    if ((releaseFactor <= 0.5) || (releaseFactor > 1.0)) {
      releaseFactor = defaultValues.getReleaseFactor();
    }
    maxBufferSize = Integer.parseInt(serviceProperties.getProperty("TOB_MaximumTotalBufferSize")
                                                      .trim());
    if ((maxBufferSize <= 500000) || (maxBufferSize >= 25000000)) {
      maxBufferSize = defaultValues.getMaxBufferSize();
    }
    maxNumberOfEntries = Integer.parseInt(serviceProperties.getProperty("TOB_MaximumNumberOfBufferEntries")
                                                           .trim());
    if ((maxNumberOfEntries <= 15) || (maxNumberOfEntries >= 5000)) {
      maxNumberOfEntries = defaultValues.getMaxNumberOfEntries();
    }
    maxDuration = Long.parseLong(serviceProperties.getProperty("TOB_MaximumBufferEntryDuration")
                                                  .trim());
    if ((maxDuration <= 3000) || (maxDuration >= 150000)) {
      maxDuration = defaultValues.getEntryDuration();
    }
    sortingAlgo = defaultValues.getSortingAlgo();
  }

  /**
   * Releases all the entries currently present in the buffer. Returns the
   * total number of entries that were released.
   *
   * @return int - the total number of entries that were released
   */
  public int releaseAllEntries() {
    int numberOfEntriesReleased = buffer.size();
    int k = buffer.size();
    int j = 0;
    //System.out.println("buffer size:" + k);
    Collections.sort(buffer, eventComparator);
    for (int i = 0; i < k; i++) {
      Object contentSynopsis = ((QosEvent) buffer.get(j)).getNBEvent()
                                .getContentSynopsis();
      if (synopsisTimeTable.containsKey(contentSynopsis)) {
        ((SynopsisTime) synopsisTimeTable.get(contentSynopsis)).setTimestamp(((QosEvent) buffer
                                                                              .get(j)).getNBEvent()
                                                                              .getEventHeaders()
                                                                              .getTimeStamp());
      }
      qosServiceImpl.onTimeOrderedDelivery((QosEvent) buffer.remove(j));
      numberOfEntries--;
    }
    totalBufferSize = 0;
    numberOfEntries = buffer.size();
    return numberOfEntriesReleased;
  }

  /**
   * Releases all the entries with the specified content synopsis.
   *
   * @param contentSynopsis Object - content synopsis
   *
   * @return int - the total number of entries that were released
   */
  public int releaseAllEntries(Object contentSynopsis) {
    int numberOfEntriesReleased = 0;
    int k = buffer.size();
    int j = 0;
    Collections.sort(buffer, eventComparator);
    for (int i = 0; i < k; i++) {
      if (((QosEvent) buffer.get(j)).getNBEvent().getContentSynopsis().equals(contentSynopsis)) {
        totalBufferSize -= ((QosEvent) buffer.get(j)).getNBEvent()
                            .getContentPayloadSize();
        if (synopsisTimeTable.containsKey(contentSynopsis)) {
          ((SynopsisTime) synopsisTimeTable.get(contentSynopsis)).setTimestamp(((QosEvent) buffer
                                                                                .get(j)).getNBEvent()
                                                                                .getEventHeaders()
                                                                                .getTimeStamp());
        }
        qosServiceImpl.onTimeOrderedDelivery((QosEvent) buffer.remove(j));
        numberOfEntriesReleased++;
        numberOfEntries--;
      } else {
        j++;
      }
    }
    numberOfEntries = buffer.size();
    return numberOfEntriesReleased;
  }

  /**
   * Releases all entries that occured before the specified timestamp.
   *
   * @param timestamp long - upper limit timestamp value of the entries to be
   * released
   *
   * @return int - the total number of entries that were released
   */
  public int releaseAllEntries(long timestamp) {
    int numberOfEntriesReleased = 0;
    int k = buffer.size();
    int j = 0;
    Collections.sort(buffer, eventComparator);
    for (int i = 0; i < k; i++) {
      if (((QosEvent) buffer.get(j)).getNBEvent().getEventHeaders()
           .getTimeStamp() <= timestamp) {
        totalBufferSize -= ((QosEvent) buffer.get(j)).getNBEvent()
                            .getContentPayloadSize();
        Object contentSynopsis = ((QosEvent) buffer.get(j)).getNBEvent()
                                  .getContentSynopsis();
        if (synopsisTimeTable.containsKey(contentSynopsis)) {
          ((SynopsisTime) synopsisTimeTable.get(contentSynopsis)).setTimestamp(((QosEvent) buffer
                                                                                .get(j)).getNBEvent()
                                                                                .getEventHeaders()
                                                                                .getTimeStamp());
        }
        qosServiceImpl.onTimeOrderedDelivery((QosEvent) buffer.remove(j));
        numberOfEntriesReleased++;
        numberOfEntries--;
      } else {
        j++;
      }
    }
    numberOfEntries = buffer.size();
    return numberOfEntriesReleased;
  }

  /**
   * Releases all entries that occured before the entry point computed with
   * release factor
   *
   * @param releaseFactor double - release factor
   *
   * @return int - the total number of entries that were released
   */
  /** . Returns the total number of entries that were released. */
  public int releaseAllEntries(double releaseFactor) {
    if (releaseFactor <= 0.5) {
      return 0;
    }
    int    numberOfEntriesReleased = 0;
    int    j            = 0;
    double temp         = getTotalNumberOfEntries() * getReleaseFactor();
    int    releasePoint = (int) Math.ceil(temp);
    Collections.sort(buffer, eventComparator);
    int k = releasePoint;
    for (int i = 0; i < k; i++) {
      totalBufferSize -= ((QosEvent) buffer.get(j)).getNBEvent()
                          .getContentPayloadSize();
      Object contentSynopsis = ((QosEvent) buffer.get(j)).getNBEvent()
                                .getContentSynopsis();
      if (synopsisTimeTable.containsKey(contentSynopsis)) {
        ((SynopsisTime) synopsisTimeTable.get(contentSynopsis)).setTimestamp(((QosEvent) buffer
                                                                              .get(j)).getNBEvent()
                                                                              .getEventHeaders()
                                                                              .getTimeStamp());
      }
      qosServiceImpl.onTimeOrderedDelivery((QosEvent) buffer.remove(j));
      numberOfEntries--;
      numberOfEntriesReleased++;
    }
    numberOfEntries = buffer.size();
    return numberOfEntriesReleased;
  }

  /**
   * Starts buffering service
   */
  public void startBufferingService() {
    bufferHandler.start();
  }

  /**
   * Stops buffering service
   */
  public void stopBufferingService() {
    bufferHandler.stopRunning();
  }

  public void terminateService() {
    bufferHandler.terminateService();
  }

  /**
   * Takes QosEvent and puts it into the buffer. If maximum number of entries
   * and/or maximum buffer size is/are reached, it notifies the BufferHandler
   * thread.
   *
   * @param qosEvent - QosEvent
   */
  public void timeOrderEvent(QosEvent qosEvent) {
    Object contentSynopsis = qosEvent.getNBEvent().getContentSynopsis();
    if (synopsisTimeTable.containsKey(contentSynopsis)) {
      long t1 = qosEvent.getNBEvent().getEventHeaders().getTimeStamp();
      long t2 = ((SynopsisTime) synopsisTimeTable.get(contentSynopsis)).getTimestamp();
      if (t1 < t2) {
        return;
      }
    }
    buffer.add(qosEvent);
    totalBufferSize += qosEvent.getNBEvent().getContentPayloadSize();
    numberOfEntries++;
    if ((totalBufferSize >= this.maxBufferSize) ||
        (numberOfEntries >= this.maxNumberOfEntries)) {
      //      System.out.println("buffer is full or max entry is reached ");
      bufferHandler.notifyBufferHandler();
    }
  }
}
